﻿using System;
using System.Collections.Generic;
using System.Linq;
using Common;
using ImageProcessing.Common;
using ImageProcessing.Matchers;

namespace ImageProcessing.Engine
{
    public class PointsResultProcessor
    {
        private readonly int _minFoundPoints;
        private readonly List<InterestPoint> _processedIpts;
        private readonly IMatcher _primaryMatcher;
        private readonly IMatcher _regionMatcher;

        private Dictionary<TagInfo, List<FeaturesFound>> _regionalPoints;
        private Dictionary<TagInfo, List<FeaturesFound>> _primaryPoints;

        private readonly ConfidenceCalculator _confidenceCalculator;
        private const int PrimaryMatcherThresholdForRegionLearning = 70;

        public bool HasPrimaryMatcherThresholdReached { get; private set; }

        public FoundObject BestFoundObject { get; private set; }

        public List<FoundObject> AllFoundObjects { get; private set; }

        public PointsResultProcessor(int minFoundPoints, List<InterestPoint> processedIpts, IMatcher primaryMatcher, IMatcher regionMatcher)
        {
            _minFoundPoints = minFoundPoints;
            _processedIpts = processedIpts;
            _primaryMatcher = primaryMatcher;
            _regionMatcher = regionMatcher;
            _confidenceCalculator = new ConfidenceCalculator(_processedIpts);
        }

        /// <summary>
        /// This builds the best found object and all found objects from the processed points and hnn passed into the constructor
        /// </summary>
        public void ProcessPoints()
        {
            // build all points
            Dictionary<TagInfo, List<FeaturesFound>> combinedPoints;

            // combine from regional and normal classifier
            if (_regionMatcher != null)
            {
                _regionalPoints = _regionMatcher.Classify(_processedIpts);
                _primaryPoints = _primaryMatcher.Classify(_processedIpts);
                combinedPoints = CombineClassifiedPoints(_primaryPoints, _regionalPoints);
            }
            else
            {
                combinedPoints = _primaryMatcher.Classify(_processedIpts);
            }

            List<FoundObject> foundObjects = new List<FoundObject>();

            foreach (KeyValuePair<TagInfo, List<FeaturesFound>> combinedPoint in combinedPoints)
            {
                FoundObject foundObject = new FoundObject(combinedPoint.Key, combinedPoint.Value, _confidenceCalculator.Calculate(combinedPoint.Value.Select(x => x.ComparisonPoints).ToList()));
                foundObjects.Add(foundObject);
            }


            AllFoundObjects = foundObjects.OrderByDescending(x => x.Confidence).ToList();
            BestFoundObject = AllFoundObjects.FirstOrDefault();

            if (BestFoundObject != null)
            {
                HasPrimaryMatcherThresholdReached = BestFoundObject.Confidence > PrimaryMatcherThresholdForRegionLearning
                    && BestFoundObject.FoundPointsCount > _minFoundPoints;
            }
        }

        /// <summary>
        /// Combine results from multiple clasifiers into one results list
        /// </summary>
        private Dictionary<TagInfo, List<FeaturesFound>> CombineClassifiedPoints(IDictionary<TagInfo, List<FeaturesFound>> primaryPoints, IDictionary<TagInfo, List<FeaturesFound>> regionalPoints)
        {
            Dictionary<TagInfo, List<FeaturesFound>> result = new Dictionary<TagInfo, List<FeaturesFound>>(primaryPoints);

            foreach (var regionalObjectsFound in regionalPoints)
            {
                List<FeaturesFound> foundPrimaryFeatures;
                if (result.TryGetValue(regionalObjectsFound.Key, out foundPrimaryFeatures))
                    result[regionalObjectsFound.Key] = CombineFoundFeatures(foundPrimaryFeatures, regionalObjectsFound.Value);
            }

            return result;
        }

        // todo : optimize remove linq code
        private List<FeaturesFound> CombineFoundFeatures(IEnumerable<FeaturesFound> foundPrimaryFeatures, IEnumerable<FeaturesFound> foundRegionalFeatures)
        {
            List<FeaturesFound> result = new List<FeaturesFound>(foundPrimaryFeatures);

            foreach (var foundRegionalFeature in foundRegionalFeatures)
            {
                if (result.FirstOrDefault(x => x.ComparisonPoints == foundRegionalFeature.ComparisonPoints) == null)
                    result.Add(foundRegionalFeature);
            }

            return result;
        }
    }
}
